/*
 * $Id: arr.c,v 1.11 2006-11-29 03:55:11 bacon Exp $
 */

#include <xp/bas/arr.h>
#include <xp/bas/sort.h>
#include <xp/bas/memory.h>
#include <xp/bas/assert.h>

// TODO: finish this....

#if defined(__GNUC__) 
	#define INLINE inline
#else
	#define INLINE
#endif

#define DATUM_AT(arr,index) (&(arr)->buf[(arr)->datum_size * (index)])

static INLINE int __sort_ascending_mem (
	const void* d1, const void* d2, void* arg)
{
	xp_arr_t* arr = (xp_arr_t*)arg;
	return xp_memcmp (d1, d2, arr->datum_size);
}

static INLINE int __sort_descending_mem (
	const void* d1, const void* d2, void* arg)
{
	xp_arr_t* arr = (xp_arr_t*)arg;
	return -xp_memcmp (d1, d2, arr->datum_size);
}

static INLINE int __sort_ascending_cb (
	const void* d1, const void* d2, void* arg)
{
	xp_arr_t* arr = (xp_arr_t*)arg;
	return arr->cbs->compdatum(arr, d1, d2);
}

static INLINE int __sort_descending_cb (
	const void* d1, const void* d2, void* arg)
{
	xp_arr_t* arr = (xp_arr_t*)arg;
	return -arr->cbs->compdatum(arr, d1, d2);
}


xp_arr_t* xp_arr_open (
	xp_arr_t* arr, xp_size_t capa, 
	xp_size_t datum_size, xp_arrcbs_t* cbs)
{
	if (arr == XP_NULL) {
		arr = (xp_arr_t*)xp_malloc (xp_sizeof(xp_arr_t));
		if (arr == XP_NULL) return XP_NULL;
		arr->__dynamic = xp_true;
	}
	else arr->__dynamic = xp_false;

	if (capa > 0) {
		arr->buf = (xp_byte_t*)xp_malloc (capa * datum_size);
		if (arr->buf == XP_NULL) {
			if (arr->__dynamic) xp_free (arr);
			return XP_NULL;
		}
	}
	else arr->buf = XP_NULL;

	arr->datum_size = datum_size;
	arr->size = 0;
	arr->capa = capa;
	arr->cbs = cbs;

	if (arr->cbs != XP_NULL && arr->cbs->initarr != XP_NULL) {
		arr->cbs->initarr (arr);
	}
	else xp_memset (arr->buf, 0, capa * datum_size);

	return arr;
}

void xp_arr_close (xp_arr_t* arr)
{
	xp_arr_clear (arr);

	if (arr->cbs != XP_NULL && arr->cbs->finiarr != XP_NULL) {
		arr->cbs->finiarr (arr);
	}

	if (arr->buf != XP_NULL) xp_free (arr->buf);
	if (arr->__dynamic) xp_free (arr);
}

void xp_arr_clear (xp_arr_t* arr)
{
	if (arr->cbs != XP_NULL && arr->cbs->finidatum != XP_NULL) {
		while (arr->size > 0) {
			arr->size--;
			arr->cbs->finidatum (arr, DATUM_AT(arr,arr->size));
		}
	}
	else {
		while (arr->size > 0) {
			arr->size--;
			xp_memset (DATUM_AT(arr,arr->size), 0, arr->datum_size);
		}
	}
}

xp_size_t xp_arr_getsize (xp_arr_t* arr)
{
	xp_assert (arr != XP_NULL);
	return XP_ARR_SIZE(arr);
}

xp_arr_t* xp_arr_setsize (xp_arr_t* arr, xp_size_t size)
{
	if (size > arr->capa && 
	    xp_arr_setcapa (arr, size) == XP_NULL) return XP_NULL;

	xp_assert (size <= arr->capa);
	arr->size = size;
	return arr;
}

xp_size_t xp_arr_getcapa (xp_arr_t* arr)
{
	xp_assert (arr != XP_NULL);
	return arr->capa;
}

xp_arr_t* xp_arr_setcapa (xp_arr_t* arr, xp_size_t capa)
{
	xp_byte_t* tmp;

	if (arr->size > capa) {
		xp_arr_remrange (arr, capa, arr->size - capa);
		xp_assert (arr->size <= capa);
	}

	if (capa > 0) {
		tmp = (xp_byte_t*)xp_realloc (
			arr->buf, arr->datum_size * capa);
		if (tmp == XP_NULL) return XP_NULL;
	}
	else {
		if (arr->buf != XP_NULL) xp_free (arr->buf);
		tmp = XP_NULL;
	}

	arr->buf = tmp;
	arr->capa = capa;
	/*if (arr->size > capa) arr->size = capa;*/
	
	return arr;
}

void* xp_arr_getdatum (xp_arr_t* arr, xp_size_t index)
{
	if (index >= arr->size) return XP_NULL;
	return DATUM_AT(arr,index);
}

void* xp_arr_putdatum (xp_arr_t* arr, xp_size_t index, const void* value)
{
	if (index >= arr->capa) {
		xp_size_t capa = arr->capa * 2;
		if (capa == 0) capa = 1;
		while (index >= capa) { capa = capa * 2; }
		if (xp_arr_setcapa(arr, capa) == XP_NULL) return XP_NULL;
	}

	xp_memcpy (DATUM_AT(arr,index), value, arr->datum_size);
	if (index >= arr->size) arr->size = index + 1;

	return DATUM_AT(arr,index);
}

void* xp_arr_insdatum (xp_arr_t* arr, xp_size_t index, const void* value)
{
	xp_size_t i;

	if (index >= arr->capa) {
		xp_size_t capa = arr->capa * 2;
		if (capa == 0) capa = 1;
		while (index >= capa) { capa = capa * 2; }
		if (xp_arr_setcapa(arr, capa) == XP_NULL) return XP_NULL;
	}

	for (i = arr->size; i > index; i--) {
		xp_memcpy (DATUM_AT(arr,i), DATUM_AT(arr,i-1), arr->datum_size);
	}
	xp_memcpy (DATUM_AT(arr,index), value, arr->datum_size);

	if (index > arr->size) arr->size = index + 1;
	else arr->size++;

	return DATUM_AT(arr,index);
}

xp_size_t xp_arr_remrange (
	xp_arr_t* arr, xp_size_t index, xp_size_t count)
{
	if (index >= arr->size) return 0;
	if (count > arr->size - index) count = arr->size - index;

// TODO: arr->cbs->finidatum
	xp_memcpy (DATUM_AT(arr,index), DATUM_AT(arr,index+count),
		arr->datum_size * (arr->size - (index + count)));

	arr->size -= count;
	return count;
}

void* xp_arr_adddatum (xp_arr_t* arr, const void* value)
{
	return xp_arr_insdatum (arr, arr->size, value);
}

xp_size_t xp_arr_remdatum (xp_arr_t* arr, const void* value)
{
	xp_size_t i = 0, count = 0;

	if (arr->cbs != XP_NULL && arr->cbs->compdatum != XP_NULL) {
		while (i < arr->size) {
			if (arr->cbs->compdatum(arr, DATUM_AT(arr,i), value) == 0) {
				if (xp_arr_remrange (arr, i, 1) == 0) count++;
				break;
			}
			i++;
		}
	}
	else {
		while (i < arr->size) {
			if (xp_memcmp(DATUM_AT(arr,i), value, arr->datum_size) == 0) {
				if (xp_arr_remrange (arr, i, 1) == 0) count++;
				break;
			}
			i++;
		}
	}

	return count;
}

xp_size_t xp_arr_remall (xp_arr_t* arr, const void* value)
{
	xp_size_t i = 0, count = 0;

	if (arr->cbs != XP_NULL && arr->cbs->compdatum != XP_NULL) {
		while (i < arr->size) {
			if (arr->cbs->compdatum(arr, DATUM_AT(arr,i), value) == 0) {
				if (xp_arr_remrange (arr, i, 1) == 0) count++;
			}
		}
	}
	else {
		while (i < arr->size) {
			if (xp_memcmp(DATUM_AT(arr,i), value, arr->datum_size) == 0) {
				if (xp_arr_remrange (arr, i, 1) == 0) count++;
			}
			else i++;
		}
	}

	return count;
}

xp_size_t xp_arr_fill (xp_arr_t* arr,
	xp_size_t index, xp_size_t count, const void* value)
{
	xp_size_t i, end_index = index + count;

	for (i = index; i < end_index; i++) {
		if (xp_arr_putdatum(arr, i, value) == XP_NULL) {
			return i - index;
		}
	}

	xp_assert (i - index == count);
	return count;
}

xp_size_t xp_arr_find (
	xp_arr_t* arr, const void* value, xp_size_t index)
{
	xp_size_t i;

	if (arr->cbs != XP_NULL && arr->cbs->compdatum != XP_NULL) {
		for (i = index; i < arr->size; i++) {
			if (arr->cbs->compdatum(
				arr, DATUM_AT(arr,i), value) == 0) return i;
		}
	}
	else {
		for (i = index; i < arr->size; i++) {
			if (xp_memcmp(DATUM_AT(arr, i),
				value, arr->datum_size) == 0) return i;
		}
	}

	return XP_ARR_INVALID_INDEX;
}

xp_size_t xp_arr_rfind (
	xp_arr_t* arr, const void* value, xp_size_t index)
{
	xp_size_t i;

	if (index >= arr->size) return (xp_size_t)-1;
		
	if (arr->cbs != XP_NULL && arr->cbs->compdatum != XP_NULL) {
		for (i = index + 1; i-- > 0; ) {
			if (arr->cbs->compdatum(
				arr, DATUM_AT(arr,i), value) == 0) return i;
		}
	}
	else {
		for (i = index + 1; i-- > 0; ) {
			if (xp_memcmp(DATUM_AT(arr,i), 
				value, arr->datum_size) == 0) return i;
		}
	}

	return XP_ARR_INVALID_INDEX;
}

int xp_arr_sort (xp_arr_t* arr, 
	xp_size_t index, xp_size_t count, int direction)
{
	if (index >= arr->size) return 0;
	if (count > arr->size - index) count = arr->size - index;

	if (direction == XP_ARR_SORT_ASCENDING) {
		xp_qsort (DATUM_AT(arr,index), count, arr->datum_size, arr, 
			((arr->cbs != XP_NULL && arr->cbs->compdatum != XP_NULL)?
				__sort_ascending_cb: __sort_ascending_mem));
		return 0;
	}
	else if (direction == XP_ARR_SORT_DESCENDING) {
		xp_qsort (DATUM_AT(arr,index), count, arr->datum_size, arr, 
			((arr->cbs != XP_NULL && arr->cbs->compdatum != XP_NULL)?
				__sort_descending_cb: __sort_descending_mem));
		return 0;
	}

	return -1;
}

xp_size_t xp_arr_walk (
	xp_arr_t* arr, xp_size_t index, 
	xp_ssize_t (*func) (xp_arr_t*, xp_size_t, void*))
{
	xp_size_t count = 0;
	xp_ssize_t n = 0;

	do {
		if (n < 0 && -n > index) break;
		if ((index += n) >= arr->size) break;
		n = func (arr, index, DATUM_AT(arr,index));
	} while (n != 0);

	return count;
}
